import json
import logging
import os
import sys
import threading
import time
import traceback
from configparser import ConfigParser
from time import sleep
import global_path

from loguru import logger

from binance.error import ClientError
from binance.um_futures import UMFutures
from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient
from running import utils
from running.account import Account
from running.position import Position
from running.turtle import Turtle, STATUS_NO_POSITION, STATUS_PENDING_OPENING_ORDER, STATUS_HOLDING, \
    STATUS_UNKNOWN_ERROR, EXIT_MODE_STOP_LOSE, EXIT_MODE_PERIOD_LOW
import pandas as pd
import numpy as np

logger.add("quant.txt", filter=lambda record: record["extra"]["name"] == "service_log", rotation="00:00")
logger.add("record.txt", filter=lambda record: record["extra"]["name"] == "app_log", rotation="00:00")
info_logger = logger.bind(name="service_log")
record_logger = logger.bind(name="app_log")

# ticker间隔
INTERVAL_S = 1
# 更新持仓信息间隔
UPDATE_POSITION_INTERVAL = 1
# 更新余额信息间隔
UPDATE_BALANCE_INTERVAL = 1
# 缓存文件
SESSION_FILE = "./session.txt"
EXCHANGE_FILE = "./exchange.txt"
STOP_FILE = "./stop.txt"
# RECORD EVENT
EVENT_OPEN = "OPEN"
EVENT_ADD = "ADD"
EVENT_CLOSE_WIN = "CLOSE_WIN"
EVENT_CLOSE_LOSE = "CLOSE_LOSE"
# 配置文件
CONFIG_FILE = "turtle-config.cfg"
# 检查状态，超过1分钟不对则重连
RECONNECT_REIGGER_COUNT = 20
TRUE = "True"
FALSE = "False"

SEND_MSG_INTERVAL = 3600
WAIT_AFTER_CLOSE = 10
# 每300个ticker重启一次，即10分钟，防止有些信号没有收到的情况
RESTART_TICKER_COUNT = 864000
STATUS_INTERVAL = 30 * 60
STATUS_QUOTE_VOLUME = 200000000
UPDATE_SERVERTIME_INTERNAL = 60

REFRESH_ORDER_INTERNAL = 60


class Quant:
    def __init__(self):
        self.lock = threading.Lock()
        # 持仓dict
        self.positions_long = {}
        self.positions_short = {}
        # 当前委托
        self.orders = {}
        # 价格表
        self.symbol_price = {}
        self.account = Account()
        self.use_proxy = False
        self.proxy = None
        self.ticker_count = 0
        self.servertime = 0
        self.last_kline_ts = 0
        self.op_history = []
        self.op_history_long = {}
        self.op_history_short = {}
        self.orders = {}
        self.position = None
        self.last_refresh_order_ts = 0
        self.usdt_balance = 0
        self.open_times = 0
        self.lose_times = 0
        self.win_times = 0
        self.turtles = []

        # kline 更新后等几个ticker再更新
        self.wait_update_kline_ticker = 0

    # 初始化
    def on_init(self):
        info_logger.warning("********** quant init **********")

        # 读取配置文件
        self.read_configs()

        has_session_file = os.path.exists(SESSION_FILE)
        if has_session_file:
            self.turtles = self.read_session_from_file()
        else:
            self.turtles = self.init_turtle_from_config()

        self.init_clients()

        # self.read_precision()

        self.subscribe_and_read()

        self.init_accounts()

        now = int(time.time())
        record_logger.info(f"开始运行 {utils.timestamp_to_string(now)} 余额:{self.usdt_balance}")
        # self.init_martins()
        self.last_init_martin_time = now

        # self.update_symbol_status()
        info_logger.warning("running on %s,%s" % (self.account.account, self.account.g_api_key))

    def init_accounts(self):
        try:
            balance_list = self.um_futures_client.balance(recvWindow=6000)
            for b in balance_list:
                if b['asset'] == "USDT":
                    self.usdt_balance = float(b['balance'])
            info_logger.info(balance_list)
        except ClientError as error:
            info_logger.error(
                "Found error. status: {}, error code: {}, error message: {}".format(
                    error.status_code, error.error_code, error.error_message
                )
            )

    def read_configs(self):
        cp = ConfigParser()
        cp.read(CONFIG_FILE)
        self.account.account = cp.get("quant", "account")
        self.account.g_api_key = cp.get("quant", "g_api_key")
        self.account.g_secret_key = cp.get("quant", "g_secret_key")
        self.use_proxy = cp.getboolean("quant", "use_proxy")
        self.proxy = eval(cp.get("quant", "proxy"))

    def init_turtle_from_config(self):
        cp = ConfigParser()
        cp.read(CONFIG_FILE)

        symbols = cp.get("quant", "symbols").split(",")
        turtleList = []
        for s in symbols:
            turtle = Turtle()
            turtle.trade_symbol = cp.get(s, "symbol")
            turtle.atr_period = cp.getint(s, "atr_period")
            turtle.period = cp.get(s, "period")
            turtle.position_amount = cp.getfloat(s, "position_amount")
            turtle.leverage = cp.getint(s, "leverage")
            turtle.price_precision = cp.getint(s, "price_precision")
            turtle.quantity_precision = cp.getint(s, "quantity_precision")
            turtle.tick_price_precision = cp.getint(s, "tick_price_precision")
            turtle.min_qty = cp.getfloat(s, "min_qty")
            turtle.entry_period = cp.getint(s, "entry_period")
            turtle.exit_period = cp.getint(s, "exit_period")
            turtle.max_stage = cp.getint(s, "max_stage")
            turtleList.append(turtle)

        return turtleList

    def read_precision(self):
        ei = self.um_futures_client.exchange_info()
        for e in ei['symbols']:
            if e['symbol'] == self.symbol.trade_symbol:
                self.symbol.price_precision = e['pricePrecision']
                self.symbol.quantity_precision = e['quantityPrecision']

    def init_clients(self):
        # UMFutures
        if self.use_proxy:
            self.um_futures_client = UMFutures(key=self.account.g_api_key, secret=self.account.g_secret_key,
                                               proxies=self.proxy)
        else:
            self.um_futures_client = UMFutures(key=self.account.g_api_key, secret=self.account.g_secret_key)

        # UMFuturesWebsocketClient
        if self.use_proxy:
            self.um_futures_ws_client = UMFuturesWebsocketClient(on_message=self.umFuturesWebsocketClientHandler,
                                                                 proxies=self.proxy)
        else:
            self.um_futures_ws_client = UMFuturesWebsocketClient(on_message=self.umFuturesWebsocketClientHandler)

    def subscribe_and_read(self):
        self.subscribe()

    def subscribe(self):
        try:
            info_logger.warning("subscribe")
            # UserData
            response = self.um_futures_client.new_listen_key()
            self.um_futures_ws_client.user_data(
                listen_key=response["listenKey"],
                id=1,
            )

            # ticker
            for t in self.turtles:
                self.um_futures_ws_client.mini_ticker(id=2, symbol=t.trade_symbol)
        except Exception as e:
            info_logger.warning("subscribe_userdata error")
            info_logger.warning(str(e))
            return None

    # 数组中是否有重复元素
    def is_duplicate_array(self, symbols):
        return len(symbols) != len(set(symbols))

    def update_symbol_status(self, symbol):
        # info_logger.warning("********** update_symbol_status **********")
        klines = self.um_futures_client.klines(limit=symbol.max_breakout_length * 2 + 10,
                                               symbol=symbol.trade_symbol,
                                               interval=symbol.period)
        symbol.current_kline = klines[-1]

        this_kline_ts = klines[-1][0]
        # info_logger.warning(f"********** this_kline_ts：{utils.timestamp_to_string(this_kline_ts/1000)}  "
        #                f"last_kline_ts:{utils.timestamp_to_string(self.last_kline_ts/1000)} **********")
        if symbol.last_kline_ts == this_kline_ts:
            return

        info_logger.warning("********** DO update_symbol_status **********")
        symbol.last_kline_ts = this_kline_ts
        symbol.pivot_high = []
        symbol.pivot_low = []
        start = len(klines) - symbol.max_breakout_length - 1
        end = len(klines)

        high = 0
        low = 9999999
        # 先计算最高点和最低点，计算chwidth
        for i in range(start, end):
            kline = klines[i]
            high = max(high, float(kline[2]))
            low = min(low, float(kline[3]))
        chwidth = round((high - low) * self.symbol.threshold_rate, symbol.price_precision)

        # 再遍历每根k线，看是不是pivothigh/pivotlow
        # 靠近两侧的不算pivot
        for i in range(start + symbol.pivot_stage, end - symbol.pivot_stage):
            kline = klines[i]
            is_pivot_high = True
            is_pivot_low = True
            kline_t = kline[0]
            kline_open = float(kline[1])
            kline_high = float(kline[2])
            kline_low = float(kline[3])
            kline_close = float(kline[4])
            left = max(start, i - symbol.pivot_stage)
            right = min(end, i + symbol.pivot_stage)
            for ii in range(left, right):
                compare_kline = klines[ii]
                compare_high = float(compare_kline[2])
                compare_low = float(compare_kline[3])
                # 跳过自己
                if compare_kline[0] == kline[0]:
                    continue
                if compare_high > kline_high:
                    is_pivot_high = False
                if compare_low < kline_low:
                    is_pivot_low = False
                if not is_pivot_high and not is_pivot_low:
                    break

            if is_pivot_high:
                # # 每根k线单独计算chwidth
                # hgst = 0
                # lwst = 9999999
                # for j in range(i - symbol.max_breakout_length, i):
                #     kl = klines[j]
                #     hgst = max(hgst, float(kl[2]))
                #     lwst = min(lwst, float(kl[3]))
                # chwidth = round((hgst - lwst) * symbol.threshold_rate, symbol.price_precision)

                # info_logger.warning(
                #     f"pivot high {kline_t} {utils.timestamp_to_string(kline_t / 1000)} h:{kline_high} l:{kline_low}")

                # 如果和前一个pivot高点相同，则把前一个换成现在这个
                if len(symbol.pivot_high) > 0 and symbol.pivot_high[-1]['h'] == kline_high:
                    symbol.pivot_high[-1] = {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                                             "o": kline_open, "c": kline_close, "chwidth": chwidth,
                                             "low_band": round(kline_high - chwidth, symbol.price_precision),
                                             "test_times": 0, "is_break": False}
                else:
                    symbol.pivot_high.append(
                        {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                         "o": kline_open, "c": kline_close, "chwidth": chwidth,
                         "low_band": round(kline_high - chwidth, symbol.price_precision),
                         "test_times": 0, "is_break": False})

            if is_pivot_low:
                # #每根k线单独计算chwidth
                # hgst = 0
                # lwst = 9999999
                # for j in range(i - symbol.max_breakout_length, i):
                #     kl = klines[j]
                #     hgst = max(hgst, float(kl[2]))
                #     lwst = min(lwst, float(kl[3]))
                # chwidth = round((hgst - lwst) * symbol.threshold_rate, symbol.price_precision)
                # info_logger.warning(
                #     f"pivot low {kline_t} {utils.timestamp_to_string(kline_t / 1000)} h:{kline_high} l:{kline_low}")
                # 如果和前一个pivot低点相同，则把前一个换成现在这个
                if len(symbol.pivot_low) > 0 and symbol.pivot_low[-1]['l'] == kline_low:
                    symbol.pivot_low[-1] = {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                                            "o": kline_open, "c": kline_close, "chwidth": chwidth,
                                            "high_band": round(kline_low + chwidth, symbol.price_precision),
                                            "test_times": 0, "is_break": False}
                else:
                    symbol.pivot_low.append(
                        {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                         "o": kline_open, "c": kline_close, "chwidth": chwidth,
                         "high_band": round(kline_low + chwidth, symbol.price_precision),
                         "test_times": 0, "is_break": False})

        # 再遍历每个pivot point,计算test times以及是否突破
        for i in range(0, len(symbol.pivot_high)):
            ph = symbol.pivot_high[i]
            h = ph["h"]
            chwidth = ph['chwidth']
            # if ph['h'] == 83230.6:
            #     print()
            for k in range(ph['index'] + 1, end):
                compare_high = round(float(klines[k][2]), symbol.price_precision)
                if compare_high > h:
                    ph['is_break'] = True
                    break
            # 先比较左边
            for j in range(1, i + 1):
                # i-1 to 0
                left_pivot_index = i - j
                compare_kline = symbol.pivot_high[left_pivot_index]
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']

                if compare_kline['h'] > ph['h']:
                    break
                if h - chwidth <= compare_high <= h and (ph['index'] - compare_kline['index'] > symbol.pivot_stage):
                    ph["test_times"] = ph["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - ph['t'] / 1000) / 60,
                                                              symbol.price_precision)
                    minutes_between_compare_and_current = round((this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60,
                                                                symbol.price_precision)
                    info_logger.warning(
                        f"increase high test times {utils.timestamp_to_string(ph['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

            # 再比较右边
            for j in range(i + 1, len(symbol.pivot_high)):
                compare_kline = symbol.pivot_high[j]
                if ph['is_break']:
                    continue
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']
                if h - chwidth <= compare_high <= h and (end - compare_kline['index'] > symbol.pivot_stage):
                    ph["test_times"] = ph["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - ph['t'] / 1000) / 60,
                                                              symbol.price_precision)
                    minutes_between_compare_and_current = round((this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60,
                                                                symbol.price_precision)
                    info_logger.warning(
                        f"increase high test times {utils.timestamp_to_string(ph['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

                if compare_high > h:
                    ph["is_break"] = True
                    compare_kline_index = compare_kline['index']
                    ph_index = ph['index']
                    current_index = len(klines) - 1
                    info_logger.warning(
                        f"break high {utils.timestamp_to_string(ph['t'] / 1000)} {h}  <==>  {utils.timestamp_to_string(compare_kline['t'] / 1000)}"
                        f" {compare_high} {compare_kline_index - ph_index} {current_index - compare_kline_index}")
                    break

        for ph in symbol.pivot_high:
            info_logger.warning(
                f"{utils.timestamp_to_string(ph['t'] / 1000)} h:{ph['h']} l:{ph['l']} chwidth:{ph['chwidth']} low_band:{ph['low_band']} test:{ph['test_times']} is_break:{ph['is_break']}")

        # 再遍历每个pivot point,计算test times以及是否突破
        for i in range(0, len(symbol.pivot_low)):
            pl = symbol.pivot_low[i]
            l = pl["l"]
            chwidth = pl['chwidth']
            # if pl['l'] == 81755.0:
            #     print()
            for k in range(pl['index'] + 1, end):
                compare_low = round(float(klines[k][3]), symbol.price_precision)
                if compare_low < l:
                    pl['is_break'] = True
                    break
            # 先比较左边
            for j in range(1, i + 1):
                # i-1 to 0
                left_pivot_index = i - j
                compare_kline = symbol.pivot_low[left_pivot_index]
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']

                if compare_low < pl['l']:
                    break
                if l + chwidth >= compare_low >= l and (pl['index'] - compare_kline['index'] > symbol.pivot_stage):
                    pl["test_times"] = pl["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - pl['t'] / 1000) / 60, 2)
                    minutes_between_compare_and_current = round(
                        (this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60, symbol.price_precision)
                    info_logger.warning(
                        f"increase low test times {utils.timestamp_to_string(pl['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

            # 再比较右边
            for j in range(i + 1, len(symbol.pivot_low)):
                compare_kline = symbol.pivot_low[j]
                if pl['is_break']:
                    continue
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']
                if l + chwidth >= compare_low >= l and (end - compare_kline['index'] > symbol.pivot_stage):
                    pl["test_times"] = pl["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - pl['t'] / 1000) / 60, 2)
                    minutes_between_compare_and_current = round(
                        (this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60, symbol.price_precision)
                    info_logger.warning(
                        f"increase low test times {utils.timestamp_to_string(pl['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

                if compare_low < l:
                    pl["is_break"] = True
                    compare_kline_index = compare_kline['index']
                    pl_index = pl['index']
                    current_index = len(klines) - 1
                    info_logger.warning(
                        f"break low {utils.timestamp_to_string(pl['t'] / 1000)} {l}  <==>  {utils.timestamp_to_string(compare_kline['t'] / 1000)} {compare_low}"
                        f" {compare_kline_index - pl_index} {current_index - compare_kline_index}")
                    break
        for pl in symbol.pivot_low:
            info_logger.warning(
                f"{utils.timestamp_to_string(pl['t'] / 1000)} h:{pl['h']} l:{pl['l']} chwidth:{pl['chwidth']} high_band:{pl['high_band']} test:{pl['test_times']} is_break:{pl['is_break']}")

    def get_breaking_pivots(self, symbol):
        breaking_ph_list = []
        breaking_pl_list = []
        for ph in symbol.pivot_high:
            if ph['is_break']:
                continue
            open = float(symbol.current_kline[1])
            high = float(symbol.current_kline[2])
            low = float(symbol.current_kline[3])
            close = float(symbol.current_kline[4])
            if ph['test_times'] >= symbol.min_number_of_test and (close > ph['h'] or high > ph['h']):
                info_logger.warning(
                    f"pivot high is break "
                    f"{utils.timestamp_to_string(ph['t'] / 1000)} "
                    f"h:{ph['h']} l:{ph['l']} low_band:{ph['low_band']} "
                    f"test:{ph['test_times']} is_break:{ph['is_break']} current h:{high} close:{close}")
                # self.exit()
                width = close - ph['low_band']
                breaking_ph_list.append(ph)
                # self.open_long(ph['t'], ph['h'], ph['h'] - self.symbol.stop_lose_ratio * width,
                #                ph['h'] + self.symbol.profit_ratio * width)

            if ph['test_times'] < symbol.min_number_of_test and (close > ph['h'] or high > ph['h']):
                ph['is_break'] = True
                info_logger.warning(
                    f"pivot high is break no enough test "
                    f"{utils.timestamp_to_string(ph['t'] / 1000)} "
                    f"h:{ph['h']} l:{ph['l']} low_band:{ph['low_band']} "
                    f"test:{ph['test_times']} is_break:{ph['is_break']}  current h:{high} close:{close}")
                # self.exit()

        # 当有多个break的时候，只用第一个，越前面越有效
        if len(breaking_ph_list) > 1:
            breaking_ph_list = [breaking_ph_list[0]]

        for pl in symbol.pivot_low:
            if pl['is_break']:
                continue
            open = float(symbol.current_kline[1])
            high = float(symbol.current_kline[2])
            low = float(symbol.current_kline[3])
            close = float(symbol.current_kline[4])
            if pl['test_times'] >= symbol.min_number_of_test and (close < pl['l'] or low < pl['l']):
                info_logger.warning(
                    f"pivot low is break "
                    f"{utils.timestamp_to_string(pl['t'] / 1000)} "
                    f"h:{pl['h']} l:{pl['l']} high_band:{pl['high_band']} "
                    f"test:{pl['test_times']} is_break:{pl['is_break']}  current l:{low} close:{close}")
                # self.exit()
                width = pl['high_band'] - close
                # self.open_short(pl['t'], pl['l'], pl['l'] + self.symbol.stop_lose_ratio * width,
                #                 pl['l'] - self.symbol.profit_ratio * width)
                breaking_pl_list.append(pl)

            if pl['test_times'] < symbol.min_number_of_test and (close < pl['l'] or low < pl['l']):
                pl['is_break'] = True
                info_logger.warning(
                    f"pivot low is break no enough test "
                    f"{utils.timestamp_to_string(pl['t'] / 1000)} "
                    f"h:{pl['h']} l:{pl['l']} high_band:{pl['high_band']} "
                    f"test:{pl['test_times']} is_break:{pl['is_break']}  current l:{low} close:{close}")
                # self.exit()
        # 当有多个break的时候，只用第一个，越前面越有效
        if len(breaking_pl_list) > 1:
            breaking_pl_list = [breaking_pl_list[0]]

        # if len(symbol.pivot_high):
        #     breaking_ph_list.append(symbol.pivot_high[-1])
        # #
        # if len(symbol.pivot_low):
        #     breaking_pl_list.append(symbol.pivot_low[-1])
        return breaking_ph_list, breaking_pl_list

    def open_long(self, t, price, stop_lose_price, stop_win_price):
        for o in self.op_history:
            strs = o.split("=")
            str_t = utils.timestamp_to_string(t / 1000)
            if str_t == strs[2]:
                return
        self.op_history.append(
            f"buy={utils.timestamp_to_string(self.servertime / 1000)}={utils.timestamp_to_string(t / 1000)}={price}={stop_lose_price}={stop_win_price}")

    def open_short(self, t, price, stop_lose_price, stop_win_price):
        for o in self.op_history:
            strs = o.split("=")
            str_t = utils.timestamp_to_string(t / 1000)
            if str_t == strs[2]:
                return
        self.op_history.append(
            f"sell={utils.timestamp_to_string(self.servertime / 1000)}={utils.timestamp_to_string(t / 1000)}={price}={stop_lose_price}={stop_win_price}")

    def on_ticker(self):

        # if self.close_wrong_positions():
        #     return
        # self.log_status()
        if self.has_stop_file():
            self.kill()
            return

        self.update_server_time()
        self.refresh_orders()
        for t in self.turtles:
            self.roll_symbol_status(t)
        self.ticker_count += 1

        self.log_status()
        self.save_session_to_file()

    def roll_symbol_status(self, turtle):
        if turtle.status == STATUS_NO_POSITION:
            self.handle_no_position(turtle)
        elif turtle.status == STATUS_PENDING_OPENING_ORDER:
            self.handle_pending_opening_order(turtle)
        elif turtle.status == STATUS_HOLDING:
            self.handle_holding(turtle)
        elif turtle.status == STATUS_UNKNOWN_ERROR:
            self.handle_unknown_error(turtle)

    def handle_no_position(self, turtle):
        # info_logger.warning("========== handle_no_position ==========")
        # if turtle.current_price == 0:
        #     return
        self.update_turtle_entry_when_kline_change(turtle)

        if turtle.long_entry_1 == 0:
            return

        if turtle.current_price > turtle.long_entry_1:
            position_side = "LONG"
            turtle.quantity += turtle.qty_n
            turtle.position_side = position_side
            turtle.stop_lose_price = round(turtle.long_entry_1 - 2 * turtle.n, turtle.price_precision)

            self.change_leverage(turtle.trade_symbol, turtle.leverage)
            info_logger.warning(f"handle_no_position open_position_market {turtle.current_price} {turtle.long_entry_1}")
            open_order_id = self.open_position_market(turtle.trade_symbol, position_side, turtle.qty_n)
            turtle.stage += 1
            turtle.open_price = turtle.current_price
            turtle.gen_next_buy_prices()
            info_logger.warning(
                f"open_order_id:{open_order_id} next_buy:{turtle.get_next_buy_price()} stage:{turtle.stage}")
            o = {'X': "NEW"}
            self.orders[open_order_id] = o
            self.open_times += 1
            self.update_balance()
            self.log_open(turtle)
            turtle.open_order_id = open_order_id
            turtle.last_long_exit_1 = turtle.long_exit_1
            turtle.status = STATUS_PENDING_OPENING_ORDER
            info_logger.warning(f"status==>STATUS_PENDING_OPENING_ORDER")

    def log_turtles(self):
        info_logger.warning("========== log_turtles ==========")
        str = ""
        for t in self.turtles:
            str += f"Turtle:{t.trade_symbol} {utils.timestamp_to_string(t.last_kline_ts / 1000)} 价：{t.current_price} n:{t.n} 入1:{t.long_entry_1} 退1:{t.long_exit_1} 损:{t.stop_lose_price} 量:{t.qty_n} \n"
        info_logger.warning(str)

    def update_turtle_entry(self, klines, turtle):
        info_logger.warning("========== update_turtle_entry ==========")
        df = pd.DataFrame(klines, columns=[
            'timestamp', 'open', 'high', 'low', 'close', 'volume',
            'close_time', 'quote_asset_volume', 'trades',
            'taker_buy_base', 'taker_buy_quote', 'ignore'
        ])
        df[['high', 'low', 'close']] = df[['high', 'low', 'close']].astype(float)
        df['prev_close'] = df['close'].shift(1)

        # 计算真实波幅TR（True Range）
        df['tr1'] = df['high'] - df['low']  # 当日最高价-最低价
        df['tr2'] = abs(df['high'] - df['prev_close'])  # 当日最高价-前日收盘价
        df['tr3'] = abs(df['low'] - df['prev_close'])  # 当日最低价-前日收盘价
        df['tr'] = df[['tr1', 'tr2', 'tr3']].max(axis=1)

        # 初始化N值序列
        df['n'] = np.nan

        # 计算初始N值（前window日的TR简单平均）
        initial_n = df['tr'].rolling(turtle.atr_period).mean()
        df.loc[turtle.atr_period - 1, 'n'] = initial_n[turtle.atr_period - 1]

        # 动态更新N值（指数平滑）
        for i in range(turtle.atr_period, len(df)):
            df.loc[i, 'n'] = ((turtle.atr_period - 1) * df.loc[i - 1, 'n'] + df.loc[i, 'tr']) / turtle.atr_period

        df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True).dt.tz_convert('Asia/Shanghai')
        df.set_index('datetime', inplace=True)

        df['long_entry_1'] = df['high'].rolling(turtle.entry_period, closed="left").max()
        df['long_exit_1'] = df['low'].rolling(turtle.exit_period, closed="left").min()
        df = df.drop(['tr1', 'tr2', 'tr3', 'prev_close'], axis=1)

        turtle.n = round(df.iloc[-2]['n'], turtle.price_precision)

        # TODO TEST
        turtle.long_entry_1 = df.iloc[-1]['long_entry_1']
        turtle.long_exit_1 = df.iloc[-1]['long_exit_1']
        # TODO TEST
        # turtle.qty_n = round(max(turtle.min_qty, turtle.position_amount / turtle.n), turtle.quantity_precision)
        turtle.qty_n = round(turtle.min_qty, turtle.quantity_precision)

    def change_leverage(self, trade_symbol, leverage):
        try:
            response = self.um_futures_client.change_leverage(
                symbol=trade_symbol, leverage=leverage, recvWindow=6000
            )
            info_logger.info(response)
        except ClientError as error:
            info_logger.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def handle_pending_opening_order(self, turtle):
        order = None
        if turtle.open_order_id in self.orders.keys():
            order = self.orders[turtle.open_order_id]

        if order and order["X"] == "FILLED":
            if turtle.stop_lose_order_id and turtle.stop_lose_order_id in self.orders.keys():
                self.cancel_order(turtle, turtle.stop_lose_order_id)
            turtle.stop_lose_order_id = self.post_stop_lose_order(turtle)
            info_logger.warning(f"stop_lose_order_id change to {turtle.stop_lose_order_id}")
            turtle.exit_mode = EXIT_MODE_STOP_LOSE
            # turtle.take_profit_order_id = self.post_take_profit_order(turtle)
            # turtle.get_next_buy_price()
            turtle.status = STATUS_HOLDING
            info_logger.warning(f"status==>STATUS_HOLDING")
        else:
            o = self.query_order(turtle.trade_symbol, turtle.open_order_id)
            if o:
                self.orders[turtle.open_order_id]['X'] = o['status']

    def refresh_orderid(self, turtle, orderid):
        if orderid:
            if orderid not in self.orders.keys():
                o = {'X': "NEW"}
                self.orders[orderid] = o
            else:
                o = self.orders[orderid]

            if o['X'] == "NEW":
                o = self.query_order(turtle.trade_symbol, orderid)
                if o:
                    self.orders[orderid]['X'] = o['status']

    def refresh_orders(self):
        now = int(time.time())
        internal = now - self.last_refresh_order_ts
        # info_logger.warning(f"========== refresh_orders {internal} ==========")
        if now - self.last_refresh_order_ts > REFRESH_ORDER_INTERNAL:
            for t in self.turtles:
                self.refresh_orderid(t, t.open_order_id)
                self.refresh_orderid(t, t.long_exit_1_order_id)
                self.refresh_orderid(t, t.stop_lose_order_id)
            self.last_refresh_order_ts = now

    def query_order(self, trade_symbol, orderId):
        try:
            o = self.um_futures_client.query_order(
                symbol=trade_symbol, orderId=orderId, recvWindow=2000
            )
            info_logger.info(o)
            return o
        except ClientError as error:
            info_logger.error(
                f"Found error. symbol:{trade_symbol} orderId:{orderId} status: {error.status_code},"
                f" error code: {error.error_code}, error message: {error.error_message}"
            )
            return None

    def post_stop_lose_order(self, turtle):
        info_logger.warning("========== post_stop_lose_order ==========")
        info_logger.warning(
            f"SELL current:{turtle.current_price} stop_lose_price:{turtle.stop_lose_price} quantity:{turtle.quantity} side:{turtle.position_side}")
        try:
            if turtle.position_side == "LONG":
                response = self.um_futures_client.new_order(
                    symbol=turtle.trade_symbol,
                    side="SELL",
                    type="STOP_MARKET",
                    stopPrice=turtle.stop_lose_price,
                    quantity=turtle.quantity,
                    positionSide=turtle.position_side,
                )
            else:
                response = self.um_futures_client.new_order(
                    symbol=turtle.trade_symbol,
                    side="BUY",
                    type="STOP_MARKET",
                    stopPrice=turtle.stop_lose_price,
                    quantity=turtle.quantity,
                    positionSide=turtle.position_side,
                )

            info_logger.info(response)
            return response['orderId']
        except ClientError as error:
            info_logger.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def post_exit_order(self, turtle):
        info_logger.warning("========== post_exit_order ==========")
        info_logger.warning(
            f"SELL current:{turtle.current_price} long_exit_1:{turtle.long_exit_1} quantity:{turtle.quantity} side:{turtle.position_side}")
        try:
            if turtle.position_side == "LONG":
                response = self.um_futures_client.new_order(
                    symbol=turtle.trade_symbol,
                    side="SELL",
                    type="STOP_MARKET",
                    stopPrice=turtle.long_exit_1,
                    quantity=turtle.quantity,
                    positionSide=turtle.position_side,
                )
            else:
                response = self.um_futures_client.new_order(
                    symbol=turtle.trade_symbol,
                    side="BUY",
                    type="STOP_MARKET",
                    stopPrice=turtle.long_exit_1,
                    quantity=turtle.quantity,
                    positionSide=turtle.position_side,
                )

            info_logger.info(response)
            return response['orderId']
        except ClientError as error:
            info_logger.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def update_turtle_entry_when_kline_change(self, turtle):
        klines = self.um_futures_client.klines(limit=max(2 * turtle.atr_period, 2 * turtle.entry_period),
                                               symbol=turtle.trade_symbol,
                                               interval=turtle.period)
        latest_kline_ts = klines[-1][0]
        if latest_kline_ts != turtle.last_kline_ts:
            if self.wait_update_kline_ticker >= 3:
                self.update_turtle_entry(klines, turtle)
                turtle.last_kline_ts = latest_kline_ts
                self.log_turtles()
                self.wait_update_kline_ticker = 0
            else:
                self.wait_update_kline_ticker += 1
        else:
            # info_logger.warning("========== wait update_turtle_entry==========")
            pass

    def handle_holding(self, turtle):
        # info_logger.warning(f"========== handle_holding ==========")
        # info_logger.warning(
        #     f"current_price:{turtle.current_price} long_entry_1:{turtle.long_entry_1} long_exit_1:{turtle.long_exit_1} last_long_exit_1:{turtle.last_long_exit_1} stop_lose_price:{turtle.stop_lose_price} stage:{turtle.stage} n:{turtle.n} next_buy:{turtle.next_buy_prices} {turtle.get_next_buy_price()}")
        self.update_turtle_entry_when_kline_change(turtle)

        # fetch order status
        long_exit_1_order_status = ""
        stop_lose_order_status = ""
        if turtle.long_exit_1_order_id and turtle.long_exit_1_order_id in self.orders.keys():
            long_exit_1_order = self.orders[turtle.long_exit_1_order_id]
            long_exit_1_order_status = long_exit_1_order['X']
        if turtle.stop_lose_order_id and turtle.stop_lose_order_id in self.orders.keys():
            stop_lose_order = self.orders[turtle.stop_lose_order_id]
            stop_lose_order_status = stop_lose_order['X']

        if long_exit_1_order_status == "FILLED":
            info_logger.warning(" 退出订单已触发 ")
            self.win_times += 1
            self.update_balance()
            self.log_win(turtle)
            turtle.reset()
            info_logger.warning(f"status==>STATUS_NO_POSITION")
            return
        elif stop_lose_order_status == "FILLED":
            info_logger.warning(" 止损订单已触发 ")
            self.lose_times += 1
            self.update_balance()
            self.log_lose(turtle)
            turtle.reset()
            info_logger.warning(f"status==>STATUS_NO_POSITION")
            return
        # 退出点高于止损点
        info_logger.warning(
            f"check up 前退：{turtle.last_long_exit_1} 退：{turtle.long_exit_1} 损：{turtle.stop_lose_price} "
            f"开：{turtle.open_price} 单:{turtle.stop_lose_order_id} 单2:{turtle.long_exit_1_order_id}")
        if (turtle.long_exit_1 > turtle.stop_lose_price
                # and turtle.long_exit_1 > turtle.open_price
                and turtle.exit_mode == EXIT_MODE_STOP_LOSE
                and turtle.stop_lose_order_id
                and turtle.stop_lose_order_id in self.orders.keys()):
            info_logger.warning(
                f"退出点高于止损点 退：{turtle.long_exit_1} 前退:{turtle.last_long_exit_1} 损：{turtle.stop_lose_price} 单：{turtle.stop_lose_order_id}")
            self.cancel_order(turtle, turtle.stop_lose_order_id)
            turtle.stop_lose_order_id = None
            exit_order_id = self.post_exit_order(turtle)
            turtle.long_exit_1_order_id = exit_order_id
            turtle.exit_mode = EXIT_MODE_PERIOD_LOW
            info_logger.warning(f"exit order id:{turtle.long_exit_1_order_id}")

        if (turtle.long_exit_1 > turtle.last_long_exit_1
                and turtle.exit_mode == EXIT_MODE_PERIOD_LOW
                and turtle.long_exit_1_order_id
                and turtle.long_exit_1_order_id in self.orders.keys()):
            info_logger.warning(
                f"退出点上移 {turtle.long_exit_1} {turtle.last_long_exit_1} {turtle.long_exit_1_order_id}")
            self.cancel_order(turtle, turtle.long_exit_1_order_id)
            turtle.long_exit_1_order_id = None
            exit_order_id = self.post_exit_order(turtle)
            turtle.long_exit_1_order_id = exit_order_id
            info_logger.warning(f"exit order id:{turtle.long_exit_1_order_id}")

        if turtle.stage < turtle.max_stage and turtle.current_price > turtle.get_next_buy_price():
            info_logger.warning(
                f"触发加仓 {turtle.current_price} {turtle.get_next_buy_price()} {turtle.stage}")
            next_buy_price = turtle.get_next_buy_price()
            position_side = "LONG"
            turtle.quantity += round(turtle.qty_n, turtle.quantity_precision)
            turtle.position_side = position_side
            turtle.stop_lose_price = round(next_buy_price - 2 * turtle.n, turtle.price_precision)

            self.change_leverage(turtle.trade_symbol, turtle.leverage)
            open_order_id = self.open_position_market(turtle.trade_symbol, position_side, turtle.qty_n)
            turtle.stage += 1
            info_logger.warning(f"open_order_id:{open_order_id} next_buy:{turtle.next_buy_price} stage:{turtle.stage}")
            o = {'X': "NEW"}
            self.orders[open_order_id] = o
            self.update_balance()
            self.log_open(turtle)
            turtle.open_order_id = open_order_id
            turtle.status = STATUS_PENDING_OPENING_ORDER
            info_logger.warning(f"status==>STATUS_PENDING_OPENING_ORDER")

        # info_logger.warning(
        #     f"last_long_exit_1 {turtle.last_long_exit_1}=>{turtle.long_exit_1} stop_lose_price:{turtle.stop_lose_price}")
        turtle.last_long_exit_1 = turtle.long_exit_1

    def update_balance(self):
        account = self.um_futures_client.account(recvWindow=6000)
        self.usdt_balance = float(account['totalWalletBalance'])

    def log_open(self, turtle):
        now = int(time.time())
        record_logger.info(
            f"{self.open_times}|{self.win_times}|{self.lose_times} {turtle.position_side} 开仓 {utils.timestamp_to_string(now)} 价:{turtle.current_price} "
            f"损:{turtle.stop_lose_price} 量:{turtle.quantity} 余:{round(self.usdt_balance, 2)}")

    def log_win(self, turtle):
        now = int(time.time())
        record_logger.info(
            f"{self.open_times}|{self.win_times}|{self.lose_times} 止盈 {utils.timestamp_to_string(now)} 余:{round(self.usdt_balance, 2)}")
        record_logger.info("============================================================")

    def log_lose(self, turtle):
        now = int(time.time())
        record_logger.info(
            f"{self.open_times}|{self.win_times}|{self.lose_times} 止损 {utils.timestamp_to_string(now)} {turtle.stop_lose_order_id} 余:{round(self.usdt_balance, 2)}")
        record_logger.info("============================================================")

    def cancel_order(self, turtle, orderId):
        info_logger.warning(f"cancel order:{orderId}")
        try:
            response = self.um_futures_client.cancel_order(
                symbol=turtle.trade_symbol, orderId=orderId, recvWindow=2000
            )
            info_logger.info(response)
        except ClientError as error:
            info_logger.error(
                f"Found error. symbol:{turtle.trade_symbol} orderId:{orderId} status: {error.status_code},"
                f" error code: {error.error_code}, error message: {error.error_message}"
            )

    def open_position_market(self, trade_symbol, positionSide, quantity):
        info_logger.warning(f"========== open_position_market ==========")
        info_logger.warning(
            f"trade_symbol:{trade_symbol} positionSide:{positionSide} quantity:{quantity}")
        try:
            if positionSide == "LONG":
                response = self.um_futures_client.new_order(
                    symbol=trade_symbol,
                    side="BUY",
                    type="MARKET",
                    quantity=quantity,
                    positionSide=positionSide,
                )
            else:
                response = self.um_futures_client.new_order(
                    symbol=trade_symbol,
                    side="SELL",
                    type="MARKET",
                    quantity=quantity,
                    positionSide=positionSide,
                )
            info_logger.info(response)
            orderId = response['orderId']
            return orderId
        except ClientError as error:
            info_logger.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def handle_unknown_error(self, symbol):
        info_logger.warning(f"UNKNOWD ERROR {symbol.trade_symbol}")

    def get_status_string(self, turtle):
        if turtle.status == STATUS_NO_POSITION:
            return "空仓"
        elif turtle.status == STATUS_HOLDING:
            return "持仓中"
        elif turtle.status == STATUS_PENDING_OPENING_ORDER:
            return "加仓中"
        else:
            return "未知状态"

    def log_status(self):
        str = ""
        for t in self.turtles:
            if t.status == STATUS_NO_POSITION:
                str = f"====== LOG_STATUS ======" \
                      f"ticker count:{self.ticker_count} server time:{utils.timestamp_to_string(self.servertime / 1000)}" \
                      f"货币:{t.trade_symbol} 现价:{t.current_price} 状态:{self.get_status_string(t)} " \
                      f"n:{t.n} 入:{t.long_entry_1} 退:{t.long_exit_1}"
            elif t.status == STATUS_HOLDING:
                str = f"\n====== LOG_STATUS ====== \n" \
                      f"ticker count:{self.ticker_count} server time:{utils.timestamp_to_string(self.servertime / 1000)}\n" \
                      f"货币:{t.trade_symbol} 现价:{t.current_price} 状态:{self.get_status_string(t)} \n" \
                      f"n:{t.n} 开仓价:{t.open_price} 入:{t.long_entry_1} 前:{t.last_long_exit_1} 退:{t.long_exit_1}\n" \
                      f"加仓点:{t.next_buy_prices} 手数:{t.stage}\n"
                if t.exit_mode == EXIT_MODE_STOP_LOSE:
                    str += f"退出模式:止损点\n"
                else:
                    str += f"退出模式:低点\n"

                if t.stop_lose_order_id is not None:
                    if t.stop_lose_order_id in self.orders.keys():
                        o = self.orders[t.stop_lose_order_id]
                        str += f"止损单 止损点单号:{t.stop_lose_order_id} 状态:{o['X']}\n"
                    else:
                        o = self.query_order(t.trade_symbol, t.stop_lose_order_id)
                        if o:
                            str += f"止损单 止损点单号:{t.stop_lose_order_id} 状态:{o['status']} 价格:{o['stopPrice']}\n"
                else:
                    str += f"止损点单号:{t.stop_lose_order_id}\n"

                if t.long_exit_1_order_id is not None:
                    if t.long_exit_1_order_id in self.orders.keys():
                        o = self.orders[t.long_exit_1_order_id]
                        str += f"止损单 止损点单号:{t.long_exit_1_order_id} 状态:{o['X']}\n"
                    else:
                        o = self.query_order(t.trade_symbol, t.long_exit_1_order_id)
                        if o:
                            str += f"止损单 止损点单号:{t.long_exit_1_order_id} 状态:{o['status']} 价格:{o['stopPrice']}\n"
                else:
                    str += f"低点单号:{t.long_exit_1_order_id}\n"

        info_logger.warning(str)

    def update_server_time(self):
        # info_logger.warning("********** update_server_time **********")
        self.servertime = self.um_futures_client.time()['serverTime']

    def has_stop_file(self):
        return os.path.exists(STOP_FILE)

    @logger.catch
    def run(self):
        try:
            self.on_init()
            while True:
                sleep(INTERVAL_S)
                # self.lock.acquire()
                self.on_ticker()
                # self.lock.release()
        except Exception as e:
            now = int(time.time())
            traceback.print_exc()
            # 或者得到堆栈字符串信息
            info = traceback.format_exc()
            info_logger.warning("quant运行报错 %s,%s" % (utils.timestamp_to_string(time.time()), str(info)))
            self.restart()
            raise e

    def restart(self):
        info_logger.warning("restart")
        os.system("./tr.sh")
        pass

    def kill(self):
        info_logger.warning("stop running")
        os.system("./kill.sh")

    def exit(self):
        sys.exit(1)

    def umFuturesWebsocketClientHandler(self, _, message):
        # info_logger.warning(f"===== umFuturesWebsocketClientHandler =====")
        msg = json.loads(message)
        if "e" not in msg.keys():
            return
        if msg['e'] == "24hrMiniTicker":
            self.update_mini_ticker(msg)
        elif msg['e'] == "ACCOUNT_UPDATE":
            self.update_account(msg)
        elif msg['e'] == "ORDER_TRADE_UPDATE":
            self.update_order(msg)
            pass
        else:
            info_logger.warning(msg)

    def update_account(self, msg):
        info_logger.warning("========== account update ==========")
        info_logger.warning(msg)
        position_list = msg['a']['P']
        for p in position_list:
            info_logger.warning(p)
            # if p['s'] == self.symbol.trade_symbol:
            #     position = Position()
            #     position.symbol = p['s']
            #     position.positionSide = p['ps']
            #     position.positionAmt = p['pa']
            #     position.entryPrice = p['ep']
            #     self.position = position
        self.usdt_balance = float(msg['a']['B'][0]['wb'])

    def update_mini_ticker(self, msg):
        for t in self.turtles:
            if t.trade_symbol == msg['s']:
                t.current_price = round(float(msg['c']), t.price_precision)

    def update_order(self, msg):
        info_logger.warning("========== update_order ==========")
        info_logger.warning(msg)
        order = msg['o']
        orderId = msg['o']['i']
        self.orders[orderId] = order
        info_logger.warning(f"{orderId} ==> {order['X']}")

    def save_session_to_file(self):
        if len(self.turtles) == 0:
            return
        # logger.warning("***************************** 保存session至本地 *****************************")
        jsonList = []
        for t in self.turtles:
            jsonList.append(t.to_json())
        with open(SESSION_FILE, 'w') as f:
            f.write(json.dumps(jsonList))

    def read_session_from_file(self):
        info_logger.warning("***************************** 从本地读取session *****************************")
        turtles = []
        with open(SESSION_FILE, 'r') as f:
            content = f.read()
            jsonList = json.loads(content)
            for json_obj in jsonList:
                t = Turtle.parse_from_json(json_obj)
                turtles.append(t)
        return turtles


def main():
    quant = Quant()
    quant.run()


if __name__ == "__main__":
    main()
